﻿#include "../../includes/scripts/cluamanager.h"
#include "../../includes/QsLog/QsLog.h"
#include "../../includes/common/common.h"
#include "../../extends/debuger/ldb.h"

extern "C"
{
    #include "../../extends/lua5.4/lua.h"
    #include "../../extends/lua5.4/lauxlib.h"
    #include "../../extends/lua5.4/lualib.h"
}

//#include "../../extends/LuaBridge/Source/LuaBridge/LuaBridge.h"

#include <QDir>
#include <QDateTime>
#include <QDebug>
#include <QLibrary>
#include <QFileInfo>
#include <QVector>
#include <QWebSocket>
#include <algorithm>

typedef void (*reg_fun)(CLuaManager *g_luamanager,lua_State *g_state);
void reg_main2Luas(lua_State *g_lua_state);

lua_State *m_lua_state = NULL;
ldb_t     *m_lua_ldb = NULL;

initialiseSingleton(CLuaManager);

const char *scriptcontent = "function _G.reloadstring(scriptPath,scriptString)"
                            "	package.loaded[scriptPath] = nil;"
                            "	local func, err = load(scriptString);"
                            "   package.loaded[scriptPath] = pcall(func);"
                            "end "
                            "function loadscriptstring(scriptpath,scriptstring)"
                            "	reloadstring(scriptpath,scriptstring);"
                            "end";

void reload(lua_State *state, const char *file) {
    QLOG_INFO()<< "ldb reload: " << file;
}

QByteArray readfile(QString filepath)
{
    QByteArray fileBytes;

    QFile pfile;
    pfile.setFileName(filepath);

    if(pfile.open(QIODevice::ReadOnly))
    {
        fileBytes = pfile.readAll();
        pfile.close();
    }

    return fileBytes;
}

CLuaManager::CLuaManager(QObject *parent)
    : QObject(parent),
      m_debugServerPort(0)
{
    m_SystemWorkingPathTimer = new QTimer(this);
    m_SystemWorkingPathTimer->setSingleShot(true);

    m_WebSocketServer.SetNetworkFrameManager(this);

    connect(m_SystemWorkingPathTimer, SIGNAL(timeout()),
            this, SLOT(WorkingdirectoryChanged()));
    connect(&m_pSystemWorkingPathWatcher, SIGNAL(directoryChanged(QString)),
            this, SLOT(WorkingdirectoryChange(QString)));
    connect(this,SIGNAL(on_signalreloadandrunscript()),
            this,SLOT(lua_signalreloadandrunscript()),
            Qt::QueuedConnection);
}

CLuaManager::~CLuaManager()
{
    clean_system();
    m_WebSocketServer.CloseServer();

    delete m_SystemWorkingPathTimer;
    m_SystemWorkingPathTimer = NULL;
}

/**
 * @brief CLuaManager::SendLog 发送日志信息
 * @param msg 要发送的日志信息
 * @param file 日志所在文件
 * @param line 日志所在行
 * @param type 日志类型
 */
void CLuaManager::SendLog(QString msg, QString file, int line, QString type)
{
    if(msg == "" || file == "" || type == "")
        return;

    QString hearder = QString("[<%1>%2 %3:%4]").arg(
                                        type,
                                        QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"),
                                        file,
                                        QString::asprintf("%d,",line));

    QJsonObject mesObjReturn;
    mesObjReturn["mesid"] = IDD_LUA_MESSAGE_LOG;
    mesObjReturn["type"] = type;
    mesObjReturn["header"] = hearder;
    mesObjReturn["content"] = msg;

    m_WebSocketServer.SendAll(JsonToString(mesObjReturn));

    emit on_signalLog(msg,type);
}

/**
 * @brief CLuaManager::SendDebugInfo 发送调试信息
 * @param msg
 */
void CLuaManager::SendDebugInfo(QString msg)
{
    if(msg == "")
        return;

    QJsonObject mesObjReturn;
    mesObjReturn["mesid"] = IDD_LUA_MESSAGE_DEBUG;
    mesObjReturn["content"] = msg;

    m_WebSocketServer.SendAll(JsonToString(mesObjReturn));
}

/**
 * @brief CLuaManager::setCurrentWorkingPath 设置当前系统工作目录
 * @param ppath 要设置的工作目录
 */
void CLuaManager::setCurrentWorkingPath(QString ppath)
{
    //判断路径是否存在
    QDir dir(ppath);
    if(!dir.exists())
    {
        return;
    }

    m_currentWorkingPath = ppath;
    m_pSystemWorkingPathWatcher.addPath(ppath);
}

void CLuaManager::WorkingdirectoryChange(const QString &path)
{
    //判断路径是否存在
    QDir dir(path);
    if(!dir.exists())
    {
        return;
    }

    m_SystemWorkingPathTimer->start(2000);
}

/**
 * @brief CLuaManager::reloadScriptAndRunning 重新加载并运行脚本
 */
void CLuaManager::reloadScriptAndRunning(void)
{
    ldb_reset(m_lua_state);

    lua_loadscriptstring_callback("scripts\\main",
                                  readfile(m_currentWorkingPath+"/main.lua"));
    lua_setup_callback();
}

void CLuaManager::WorkingdirectoryChanged(void)
{
    emit on_signalreloadandrunscript();
}

/**
 * @brief CLuaManager::Convertpackagetofolder 将指定的包数据转换到指定的文件夹
 * @param packageData 要转换的包数据
 * @param filePath 目标目录
 * @return 如果转换成功返回真，否则返回假
 */
bool CLuaManager::Convertpackagetofolder(QByteArray packageData,QString filePath)
{
    if(packageData.isEmpty() || filePath.isEmpty())
        return false;

    //QDir dir;
    //dir.setPath(filePath);
    //dir.removeRecursively();
    removeFolderContent(filePath);

    QByteArray tempData = packageData;

    tagSVNVersionPathStruct ptagSVNVersionPathStruct;
    memset(&ptagSVNVersionPathStruct,0,sizeof(tagSVNVersionPathStruct));

    memcpy(&ptagSVNVersionPathStruct,tempData.data(),sizeof(tagSVNVersionPathStruct));
    tempData.remove(0,sizeof(tagSVNVersionPathStruct));

    if(ptagSVNVersionPathStruct.tag[0] != 'L' ||
       ptagSVNVersionPathStruct.tag[1] != 'U' ||
       ptagSVNVersionPathStruct.tag[2] != 'A')
        return false;

    tempData = qUncompress(tempData);

    for(int i=0;i<ptagSVNVersionPathStruct.fileCount;i++)
    {
        tagSVNVersionFileStruct ptagSVNVersionFileStruct;
        memset(&ptagSVNVersionFileStruct,0,sizeof(ptagSVNVersionFileStruct));

        memcpy(&ptagSVNVersionFileStruct,tempData.data(),sizeof(tagSVNVersionFileStruct));
        tempData.remove(0,sizeof(tagSVNVersionFileStruct));

        QString pfileName = QString(ptagSVNVersionFileStruct.fileName);
        QString ptmpFilePath = filePath + pfileName;

        QString tmpFileDirPath = ptmpFilePath.mid(0,ptmpFilePath.lastIndexOf(tr("/")));

        QDir dir(tmpFileDirPath);
        if(!dir.exists())
        {
            if(!dir.mkpath(tmpFileDirPath))
            {
                return false;
            }
        }

        QFile precvFile(ptmpFilePath);
        if(precvFile.open(QIODevice::WriteOnly))
        {
            QByteArray tmpFileData = tempData.mid(0,ptagSVNVersionFileStruct.fileSize);
            tempData.remove(0,ptagSVNVersionFileStruct.fileSize);

            precvFile.write(tmpFileData);
            precvFile.close();
        }
    }

    return true;
}

/**
 * @brief CLuaManager::init_system 初始化系统
 * @param workingpath 系统工作目录
 * @param regdllpath 注册的脚本dll所在目录
 * @param serverport 要打开的服务器端口
 *
 * @return 如果系统初始化成功返回真，否则返回假
 */
bool CLuaManager::init_system(QString workingpath,QString regdllpath,int serverport)
{
    if(workingpath.isEmpty())
        return false;

    // 设置工作目录
    this->setCurrentWorkingPath(workingpath);

    if(regdllpath != "")
    {
        // 设置注册脚本所在目录
        this->setRegisterDllPath(regdllpath);
    }

    // 是否启用网络
    if(serverport > 0)
    {
        m_debugServerPort = serverport;
        m_WebSocketServer.OpenServer(serverport);
    }

    m_lua_state = luaL_newstate();
    luaL_openlibs(m_lua_state);
    luaopen_cjson(m_lua_state);

    //lua_register(g_lua_state, "c_break", c_break);

    if(m_debugServerPort > 0)
        m_lua_ldb = ldb_new(m_lua_state, reload, this);

    if(!exe_lua_string(scriptcontent,strlen(scriptcontent)))
    {
        SendLog(QString::fromLocal8Bit("init_system加载脚本执行失败."), __FILE__, __LINE__,"error");
        return false;
    }

    // 注册main
    reg_main2Luas(m_lua_state);

    reg_all2luas();

    SendLog(QString::fromLocal8Bit("Lua脚本系统初始成功,工作目录:<b>")+
            workingpath+
            QString::fromLocal8Bit("</b>;插件目录:<b>")+
            regdllpath+
            QString::fromLocal8Bit("</b>;调试服务器端口:<b>")+
            QString::asprintf("%d</b>",serverport)
            , __FILE__, __LINE__,"info");

    // 加载脚本并执行
    WorkingdirectoryChanged();

    return true;
}

/**
 * @brief CLuaManager::reg_all2luas 注册所有用到的函数
 */
void CLuaManager::reg_all2luas(void)
{
    if(m_lua_state == NULL || m_currentRegisterDllPath.isEmpty())
        return;

    QVector<QString> pfilelist;
    if(FindFile(m_currentRegisterDllPath,pfilelist) < 0)
        return;

    for(int i=0;i<pfilelist.size();i++)
    {
        QFileInfo pFileInfo(pfilelist[i]);
        if(!pFileInfo.exists() || pFileInfo.suffix().toLower() != "dll")
            continue;

        QLibrary pDllLibrary(pfilelist[i]);
        if(!pDllLibrary.load())
        {
            QLOG_ERROR() << "load dll:"<<pfilelist[i]<<" fail.";
            SendLog(QString::fromLocal8Bit("导入DLL失败:")+pfilelist[i], __FILE__, __LINE__,"error");

            continue;
        }

        reg_fun reg_xxx2Luas = (reg_fun)pDllLibrary.resolve("reg_xxx2Luas");
        if(reg_xxx2Luas)
        {
            reg_xxx2Luas(this,m_lua_state);
        }

        //pDllLibrary.unload();
    }

    emit on_signalLog("Lua插件注册成功.","info");
}

/**
 * @brief CLuaManager::scriptUninstallFinished 脚本卸载完成
 */
void CLuaManager::scriptUninstallFinished(void)
{
    ldb_closesocket();

    if (m_lua_state != NULL)
    {
        if(m_lua_ldb != NULL)
        {
            ldb_free(m_lua_ldb);
            m_lua_ldb=NULL;
        }

        lua_close(m_lua_state);
        m_lua_state = NULL;
        QLOG_INFO() << "clean_system close success.";
        SendLog(QString::fromLocal8Bit("Lua脚本系统卸载成功."), __FILE__, __LINE__,"info");
    }

    QApplication::exit();
}

/**
 * @brief CLuaManager::clean_system 卸载系统
 */
void CLuaManager::clean_system(void)
{
    if(ldb_safeQuitLoop())
        scriptUninstallFinished();
}

/**
 * @brief CLuaManager::exe_lua_string 执行指定的lua脚本
 * @param luastring 要执行的lua脚本
 * @param size 脚本字段长度
 * @return 如果脚本执行成功返回真，否则返回假
 */
bool CLuaManager::exe_lua_string(const char* luastring,quint32 size)
{
    if(m_lua_state == NULL || luastring == NULL || size <= 0)
        return false;

    if (luaL_loadbuffer(m_lua_state, luastring, size, "qtCore") ||
            lua_pcall(m_lua_state, 0, 0, 0))
    {
        // 发生错误时
        const char* pErrorMsg = luaL_checkstring(m_lua_state, -1);
        QLOG_ERROR() << "exe_lua_string:" << pErrorMsg;
        SendLog(QString::fromLocal8Bit("Lua脚本执行错误:")+pErrorMsg, __FILE__, __LINE__,"error");
        return false;
    }

    QLOG_INFO() << "exe_lua_string success.";
    //SendLog(QString::fromLocal8Bit("Lua脚本执行成功."), __FILE__, __LINE__,"info");

    return true;
}

/**
 * @brief CLuaManager::lua_loadscriptstring_callback 从字符串中导入脚本
 * @param scriptpath 脚本所在目录
 * @param scriptstring 脚本字符串
 */
void CLuaManager::lua_loadscriptstring_callback(const char* scriptpath, const char* scriptstring)
{
    if (m_lua_state == NULL ||
            scriptpath == NULL ||
            scriptstring == NULL)
        return;

    int iRet = 0;

    lua_getglobal(m_lua_state, "loadscriptstring");
    lua_pushstring(m_lua_state, scriptpath);
    lua_pushstring(m_lua_state, scriptstring);
    iRet = lua_pcall(m_lua_state, 2, 0, 0);

    if (iRet)
    {
        const char *pErrorMsg = lua_tostring(m_lua_state, -1);
        QLOG_ERROR() << "lua_loadscript_callback:" << pErrorMsg;
        SendLog(QString::fromLocal8Bit("Lua脚本导入失败:")+pErrorMsg, __FILE__, __LINE__, "error");
    }
    else
    {
        QLOG_INFO() << "lua_loadscript_callback successed.";
        SendLog(QString::fromLocal8Bit("Lua脚本加载成功,开始运行..."), __FILE__, __LINE__, "info");
    }
}

#if defined (WIN32)
/**
 * @brief CLuaManager::lua_function_callback 带参数调用指定的函数
 * @param funcName
 * @param params
 * @return
 */
bool CLuaManager::lua_function_callback(QString funcName,
                           QVariantList params,
                           int returnParamCount,
                           QVariantList *returnparams)
{
    if(funcName == "" || m_lua_state == NULL) return false;

    lua_getglobal(m_lua_state, funcName.toStdString().c_str());

    if (lua_isnil(m_lua_state, -1) || !lua_isfunction(m_lua_state, -1))
    {
        SendLog(QString::fromLocal8Bit("Lua脚本函数")+
                funcName+
                QString::fromLocal8Bit("导入失败."), __FILE__, __LINE__,"error");
        return false;
    }

    for(int i=0;i<params.size();i++)
    {
        QVariant::Type ttype = params[i].type();
        switch(ttype)
        {
        case QVariant::Int:
            lua_pushinteger(m_lua_state,params[i].toInt());
            break;
        case QVariant::Double:
            lua_pushnumber(m_lua_state,params[i].toDouble());
            break;
        case QVariant::LongLong:
            lua_pushinteger(m_lua_state,params[i].toLongLong());
            break;
        case QVariant::String:
            lua_pushstring(m_lua_state,params[i].toString().toStdString().c_str());
            break;
        case QVariant::Bool:
            lua_pushboolean(m_lua_state,params[i].toBool() ? 1 : 0);
            break;
        default:
            break;
        }
    }

    int iRet = lua_pcall(m_lua_state, params.size(), returnParamCount, 0);
    if (iRet)
    {
        const char *pErrorMsg = lua_tostring(m_lua_state, -1);
        QLOG_ERROR() << "lua_function_callback:" << pErrorMsg;
        SendLog(QString::fromLocal8Bit("Lua脚本函数")+
                funcName+
                QString::fromLocal8Bit("执行失败:")+
                pErrorMsg, __FILE__, __LINE__,"error");
        return false;
    }

    if(returnParamCount > 0 && returnparams != NULL)
    {
        for(int i=0;i<returnParamCount;i++)
        {
            int index = -1;
            if(lua_isnumber(m_lua_state,index))
                returnparams->push_back(lua_tonumber(m_lua_state,index));
            else if(lua_isinteger(m_lua_state,-(i+1)))
                returnparams->push_back(lua_tointeger(m_lua_state,index));
            else if(lua_isstring(m_lua_state,-(i+1)))
                returnparams->push_back(lua_tostring(m_lua_state,index));
            else if(lua_isboolean(m_lua_state,-(i+1)))
                returnparams->push_back(lua_toboolean(m_lua_state,index));
            lua_pop(m_lua_state, 1);
        }

        std::reverse(returnparams->begin(), returnparams->end());
    }

    return true;
}
#endif

/**
 * @brief CLuaManager::lua_setup_callback 初始脚本系统
 */
void CLuaManager::lua_setup_callback(void)
{
    if(m_lua_state == NULL)
        return;

    lua_getglobal(m_lua_state, "setup");
    //if (lua_istable(m_lua_state, -1) || lua_type(m_lua_state, -1) == LUA_TTABLE)
    //{
    //   lua_getfield(m_lua_state, -1, "setup");

        if (lua_isnil(m_lua_state, -1) || !lua_isfunction(m_lua_state, -1))
        {
            SendLog(QString::fromLocal8Bit("Lua脚本函数'setup'导入失败."), __FILE__, __LINE__,"error");
            return;
        }

        int iRet = lua_pcall(m_lua_state, 0, 0, 0);
        if (iRet)
        {
            const char *pErrorMsg = lua_tostring(m_lua_state, -1);
            QLOG_ERROR() << "lua_setup_callback:" << pErrorMsg;
            SendLog(QString::fromLocal8Bit("Lua脚本函数'setup'执行失败.")+
                    pErrorMsg, __FILE__, __LINE__,"error");
        }
    //}
}

/**
 * @brief CLuaManager::lua_network_callback 网络消息处理
 * @param socketid 客户端的名称
 * @param msg 要处理的消息，这里以json格式组织消息
 */
/*void CLuaManager::lua_network_callback(QString socketid, QString msg)
{
    if (m_lua_state == NULL || socketid.isEmpty() || msg.isEmpty())
        return;

    int iRet = 0;

    lua_getglobal(m_lua_state, "main");
    if (lua_istable(m_lua_state, -1) || lua_type(m_lua_state, -1) == LUA_TTABLE)
    {
        //lua_pushstring(m_lua_state, "process_network");
        //lua_gettable(m_lua_state, -2);

        lua_getfield(m_lua_state, -1, "process_network");

        if (lua_isnil(m_lua_state, -1) || !lua_isfunction(m_lua_state,-1))
        {
            SendLog("lua load 'process_network' fail.", __FILE__, __LINE__,"info");
            return;
        }

        lua_pushnil(m_lua_state);
        lua_pushstring(m_lua_state, socketid.toStdString().c_str());
        lua_pushstring(m_lua_state, msg.toStdString().c_str());

        iRet = lua_pcall(m_lua_state, 3, 0, 0);

        if (iRet)
        {
            const char *pErrorMsg = lua_tostring(m_lua_state, -1);
            QLOG_ERROR() << "lua_network_callback:" << pErrorMsg;
            SendLog(pErrorMsg, __FILE__, __LINE__,"error");
        }
    }
}*/

/**
 * @brief CLuaManager::OnProcessConnectedNetMes 处理一个新的连接到达
 * @param conn 到达的新的连接
 */
void CLuaManager::OnProcessConnectedNetMes(QWebSocket *conn)
{

}

/**
 * @brief CLuaManager::OnProcessDisconnectedNetMes 处理一个连接关闭
 * @param conn 要断开的连接
 */
void CLuaManager::OnProcessDisconnectedNetMes(QWebSocket *conn)
{
    //ldb_quitEventLoop(true);
}

/**
 * @brief CLuaManager::OnProcessNetBinary 处理网络二进制消息
 * @param conn 要处理的客户端
 * @param data 到达的二进制消息
 */
void CLuaManager::OnProcessNetBinary(QWebSocket *conn,QByteArray &data)
{
    tagVersionOper pVersionOper;
    QByteArray reciverData = data;

    memset(&pVersionOper,0,sizeof(tagVersionOper));
    memcpy(&pVersionOper,reciverData.data(),sizeof(tagVersionOper));
    reciverData.remove(0,sizeof(tagVersionOper));

    switch(pVersionOper.tagIndex)
    {
    case IDD_LUA_MESSAGE_UPDATELUA:
    {
        Convertpackagetofolder(reciverData,m_currentWorkingPath);
    }
        break;
    default:
        break;
    }
}

/**
 * @brief CLuaManager::lua_signalreloadandrunscript 重新加载并运行脚本
 */
void CLuaManager::lua_signalreloadandrunscript(void)
{
    if(ldb_quitEventLoop())
    {
        reloadScriptAndRunning();
    }
}

/**
 * @brief CLuaManager::OnProcessNetText 处理网络字符串消息
 * @param conn 要处理的客户端
 * @param mes 到达的字符串消息
 */
void CLuaManager::OnProcessNetText(QWebSocket *conn,QString mes)
{
    QJsonObject msgObj = StringToJson(mes);

    switch(msgObj["msgId"].toInt())
    {
    case IDD_LUA_MESSAGE_RESTART:          // 重新开始脚本
    {
        emit on_signalreloadandrunscript();
    }
        break;
    default:
        break;
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////

static int c_break(lua_State *state) {
    ldb_break(state);
    return 0;
}

int setlog_tolua(lua_State* L)
{
    std::string type = luaL_checkstring(L, 2);
    std::string msg =  luaL_checkstring(L, 1);

    if (type == "error")
        QLOG_ERROR() << msg.c_str();
    else if (type == "info")
        QLOG_INFO() << msg.c_str();
    else if (type == "waring")
        QLOG_WARN() << msg.c_str();
    else if (type == "fatal")
        QLOG_FATAL() << msg.c_str();

    CLuaManager::getSingleton().SendLog(msg.c_str(), __FILE__, __LINE__,type.c_str());

    return 0;
}

const struct luaL_Reg mainLib[] =
{
    {"log", setlog_tolua},
    {"lbreak", c_break},

    {NULL, NULL}       //数组中最后一对必须是{NULL, NULL}，用来表示结束
};

void reg_main2Luas(lua_State *g_lua_state)
{
    if(g_lua_state == NULL) return;

    lua_getglobal(g_lua_state, "main");
    if (lua_isnil(g_lua_state, -1)) {
        lua_pop(g_lua_state, 1);
        lua_newtable(g_lua_state);
    }

    luaL_setfuncs(g_lua_state, mainLib, 0);
    lua_setglobal(g_lua_state, "main");
}
